#include "process.h"
#include "disk.h"
#include "schedule.h"
#include "simulator.h"
#include "tread.h"
#include "observer.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define CONTEXT_SWITCH_TIME	1


void sim_init(struct schedFuncTable *);
void sim_run_clock(int);
void sim_set_context(struct process *);

struct process *sim_get_context();
struct process *ctxt;
struct schedFuncTable *simSchedTab;
static int clock;

/* By Jacob Eichers and Cliff Buchanan 
	This is the main loop, it contains the main orchestration of the simulator 
	as it manipulates each of the parts of the simulator to achieve its purposes
	*/


int main(int argc, char **argv)
{
	int diskInt, treadInt, schedInt, procInt;
	int runtime, t;
	struct process *oldCtxt = NULL, *p = NULL;
	
	if (argc != 2)
	{
		printf("Wrong number of arguments. Required: 1\n");
		return -1;
	}
	if (argc == 2)
	{
		if (!strcmp(argv[1], "exp"))
			{sim_init(&expSchedTab);}
		else if (!strcmp(argv[1], "stcp"))	
			{sim_init(&stcpSchedTab);}
		else
		{
			printf("Incorrect argument. Options: \"exp\" or \"stcp\"\n");
			return -1;
		}
	}
	
	
	
	
	do
	{
		/*check if any interrupt has occured*/
		diskInt = !disk_done() &&
			disk_time_to_event()<=0;
		treadInt = !tread_done() &&
			tread_time_to_event()<=0;
		schedInt = !simSchedTab->done() &&
			simSchedTab->time_to_event()<=0;
		procInt = sim_get_context() &&
			proc_time_to_event(sim_get_context())<=0;

		/* an interrupt needs to be handled*/
		if (diskInt || treadInt || schedInt || procInt)
		{
			/* save last process*/
			oldCtxt = sim_get_context();
			/* swap to NULL if not already there*/
			if (oldCtxt)
				{sim_set_context(NULL);}
			
			/* diskInt: return process from disk to scheduler*/
			if (diskInt)
			{
				p = disk_return_job();
				
				proc_did_io(p, 1);
				
				/* add or exit p, just do SOMETHING*/
				if (!proc_done(p))
					{simSchedTab->add(p);}
				else
					{proc_exit(p);}
			}

			/* procInt: remove process from execution, exit or add to disk queue*/
			if (procInt)
			{
				if(proc_done(oldCtxt))
					{proc_exit(oldCtxt);}
				else
					{disk_accept_job(oldCtxt);}

				/* oldCtxt is being handled elsewhere. Delete our reference.*/
				oldCtxt = NULL;
			}

			/* treadInt: add new process(es) to the scheduler*/
			if (treadInt)
			{
				while (!tread_done() && (tread_time_to_event()<=0))
					{simSchedTab->add(tread_next());}
			}


			/* Unless the oldCtxt didn't exist or was exited, reschedule it*/
			if (oldCtxt)
				{simSchedTab->add(oldCtxt);}
			
			/* load next job, if there is one*/
			if( !(simSchedTab->done()) )
			{
				p = simSchedTab->schedule();
				sim_set_context(p);
			}
		}

		/* Calculate the time to run before something will happen*/
		runtime = 0;
		/* Flag to tell if runtime has been set yet*/
		t = 0;
		if (!disk_done() && (!t || disk_time_to_event() < runtime))
		{
			runtime = disk_time_to_event();
			t = 1;
		}

		if (!tread_done() && (!t || tread_time_to_event() < runtime))
		{
			runtime = tread_time_to_event();
			t = 1;
		}

		if (!simSchedTab->done() &&
			(!t || simSchedTab->time_to_event() < runtime))
		{
			runtime = simSchedTab->time_to_event();
			t = 1;
		}

		if (sim_get_context() && 
			(!t || proc_time_to_event(sim_get_context()) < runtime))
		{
			runtime = proc_time_to_event(sim_get_context());
			t = 1;
		}
		
		/* move sim time forward*/
		sim_run_clock(runtime);
		/* if there is a running process, record time it ran*/
		if(sim_get_context())
			{proc_ran(sim_get_context(), runtime);}
	}
	while(!tread_done() || !disk_done() || !simSchedTab->done() ||
		sim_get_context());


	obs_print_stats();
	
	return 0;
}

/* initalize the simulator*/
void sim_init(struct schedFuncTable *sched)
{
	clock = 0;
	ctxt = NULL;

	/*choose the prefered scheduler*/
	simSchedTab = sched;

	simSchedTab->init();
	obs_init();
	disk_init();
	tread_init();
}

/* change the current context of the machine and update the clock
	for the context switch delay*/
void sim_set_context(struct process *new)
{
	ctxt = new;

	sim_run_clock(SIM_CONTEXT_SWITCH_TIME);

	obs_sim_set_context(new);
}

/* return current context*/
struct process *sim_get_context()
{
	return ctxt;
}

/* return clock time (ms)*/
int sim_get_clock()
{
	return clock;
}

/* run the clock*/
void sim_run_clock(int t)
{
	clock += t;

	obs_sim_run_clock(t);
}
